package device

import (
	"context"
	"encoding/json"
	"path"
	"strconv"
	"time"

	"device-admin/config/mysql"
	"device-admin/config/redis"
	"device-admin/internal/app/grpc/protoc/devicec"
	"device-admin/internal/app/grpc/service/alarm_monitor"
	"device-admin/internal/app/grpc/service/terminal"
	"device-admin/internal/app/web/service/device"
	"device-admin/internal/app/web/service/location"
	"device-admin/internal/dal"
	"device-admin/internal/dao"
	"device-admin/internal/define"
	"device-admin/internal/models"

	"192.168.1.75/go-pkg/errorx"
	"192.168.1.75/go-pkg/go-cache"
	"192.168.1.75/go-pkg/logx"
	"github.com/golang-module/carbon/v2"
	"github.com/samber/lo"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"
)

type AdminService struct {
	devicec.UnimplementedAdminServiceServer
}

// DeviceDetail 设备详情
func (*AdminService) DeviceDetail(ctx context.Context, req *devicec.DeviceDetailReq) (*devicec.DeviceDetailRsp, error) {
	var deviceList []device.DeviceWithLocationType
	// 为避免频繁查询数据库，这里用本地缓存进行设备列表的缓存
	if val, found := cache.Get(define.CacheDeviceList); !found {
		deviceList, _ = device.GetDeviceWithLocation("", "", "", "", "", "", 0, 0, "", "", 0)
		cache.Set(define.CacheDeviceList, deviceList, time.Second*5, nil)
	} else {
		deviceList, _ = val.([]device.DeviceWithLocationType)
	}
	devices := lo.Filter(deviceList, func(device device.DeviceWithLocationType, index int) bool {
		if req.RobotId != "" {
			return device.RobotID == req.RobotId
		} else if req.IdentificationCode != "" {
			return device.IdentificationCode == req.IdentificationCode
		}
		return false
	})
	rsp := &devicec.DeviceDetailRsp{}
	if len(devices) == 0 {
		rsp.Code = -1
		rsp.Msg = "设备不存在"
	} else {
		device := devices[0]
		d := &devicec.Device{}
		d.RobotId = device.RobotID
		d.IdentificationCode = device.IdentificationCode
		d.MerchantId = device.MerchantID
		d.MerchantPath = device.MerchantPath
		d.LocationId = device.LocationID
		d.ProvinceId = device.ProvinceID
		d.CityId = device.CityID
		d.DistrictId = device.DistrictID
		d.DeviceType = int32(device.DeviceType)
		d.ServicePhone = device.ServicePhone
		d.LatestActiveTime = device.LatestActiveTime
		d.CurrentMapName = device.CurrentMapName
		d.Battery = int32(device.Battery)
		d.SaleStatus = int32(device.SaleStatus)
		d.OpeningHours = device.OpeningHours
		d.PositionId = device.PositionID
		d.Addr = device.Addr
		d.Floor = device.FloorName
		rsp.Device = d
	}
	return rsp, nil
}

// DeviceList 设备列表
func (*AdminService) DeviceList(ctx context.Context, req *devicec.DeviceListReq) (*devicec.DeviceListRsp, error) {
	devices, total := device.GetDeviceWithLocation(
		req.RobotId, "",
		req.MerchantId,
		req.ProvinceId, req.CityId, req.DistrictId,
		req.Page, req.Limit,
		req.LocationId, req.FloorId,
		int(req.SaleStatus),
	)
	rsp := &devicec.DeviceListRsp{}
	for _, device := range devices {
		d := &devicec.Device{}
		d.RobotId = device.RobotID
		d.IdentificationCode = device.IdentificationCode
		d.MerchantId = device.MerchantID
		d.MerchantPath = device.MerchantPath
		d.LocationId = device.LocationID
		d.ProvinceId = device.ProvinceID
		d.CityId = device.CityID
		d.DistrictId = device.DistrictID
		d.DeviceType = int32(device.DeviceType)
		d.ServicePhone = device.ServicePhone
		d.LatestActiveTime = device.LatestActiveTime
		d.CurrentMapName = device.CurrentMapName
		d.Battery = int32(device.Battery)
		d.SaleStatus = int32(device.SaleStatus)
		d.OpeningHours = device.OpeningHours
		d.PositionId = device.PositionID
		d.Addr = device.Addr
		d.Floor = device.FloorName
		rsp.List = append(rsp.List, d)
		rsp.Total = total
	}
	return rsp, nil
}

// LocationDetail 位置详情
func (*AdminService) LocationDetail(ctx context.Context, req *devicec.LocationDetailReq) (*devicec.LocationDetailRsp, error) {
	var location struct {
		ID           string  `json:"id" gorm:"column:id"`
		Name         string  `json:"name" gorm:"column:name"`
		ProvinceID   string  `json:"province_id" gorm:"column:province_id"`
		CityID       string  `json:"city_id" gorm:"column:city_id"`
		DistrictID   string  `json:"district_id" gorm:"column:district_id"`
		Addr         string  `json:"addr" gorm:"column:addr"`
		OpeningHours string  `json:"opening_hours" gorm:"column:opening_hours"`
		Latitude     float64 `json:"latitude" gorm:"column:latitude"`
		Longitude    float64 `json:"longitude" gorm:"column:longitude"`
	}
	rowsAffected := mysql.NewDB().
		Model(&models.Location{}).
		Where("id = ?", req.LocationId).
		Take(&location).RowsAffected
	rsp := &devicec.LocationDetailRsp{}
	if rowsAffected == 0 {
		rsp.Code = -1
		rsp.Msg = "设备不存在"
		return rsp, nil
	} else {
		var LocationPoints models.LocationPoints
		pointsCount := mysql.NewDB().Model(&models.LocationPoints{}).
			Where("location_id = ?", req.LocationId).
			Where("points_id != 0").
			Take(&LocationPoints).RowsAffected
		rsp.Location = &devicec.Location{
			Id:           location.ID,
			Name:         location.Name,
			ProvinceId:   location.ProvinceID,
			CityId:       location.CityID,
			DistrictId:   location.DistrictID,
			Addr:         location.Addr,
			OpeningHours: location.OpeningHours,
			Lng:          float32(location.Longitude),
			Lat:          float32(location.Latitude),
		}
		if pointsCount > 0 {
			rsp.Location.PointsId = LocationPoints.PointsID
			rsp.Location.PointsName = define.PointsType[rsp.Location.PointsId]
			rsp.Location.PointsSort = LocationPoints.Sort
			rsp.Location.PointsBackgroundImage = LocationPoints.BackgroundImage
			rsp.Location.PointsMallId = LocationPoints.MallID
			rsp.Location.PointsValue = float32(LocationPoints.PointsValue)
		}
		return rsp, nil
	}
}

// LocationList 位置列表
func (*AdminService) LocationList(ctx context.Context, req *devicec.LocationListReq) (*devicec.LocationListRsp, error) {
	var total int64
	var locations []struct {
		ID           string `json:"id" gorm:"column:id"`
		Name         string `json:"name" gorm:"column:name"`
		ProvinceID   string `json:"province_id" gorm:"column:province_id"`
		CityID       string `json:"city_id" gorm:"column:city_id"`
		DistrictID   string `json:"district_id" gorm:"column:district_id"`
		Addr         string `json:"addr" gorm:"column:addr"`
		OpeningHours string `json:"opening_hours" gorm:"column:opening_hours"`
	}
	tx := mysql.NewDB().Model(&models.Location{})
	tx.Count(&total)
	tx.Find(&locations)
	rsp := &devicec.LocationListRsp{}
	rsp.Total = total
	locationIDList := []string{}
	for _, v := range locations {
		locationIDList = append(locationIDList, v.ID)
	}
	var LocationPoints []models.LocationPoints
	mysql.NewDB().Model(&models.LocationPoints{}).
		Where("location_id in (?)", locationIDList).
		Where("points_id != 0").
		Find(&LocationPoints)
	PointsMap := make(map[string]models.LocationPoints)
	for _, v := range LocationPoints {
		PointsMap[v.LocationID] = v
	}
	for _, location := range locations {
		l := &devicec.Location{
			Id:           location.ID,
			Name:         location.Name,
			ProvinceId:   location.ProvinceID,
			CityId:       location.CityID,
			DistrictId:   location.DistrictID,
			Addr:         location.Addr,
			OpeningHours: location.OpeningHours,
		}
		if _, ok := PointsMap[location.ID]; ok {
			l.PointsId = PointsMap[location.ID].PointsID
			l.PointsName = define.PointsType[l.PointsId]
			l.PointsSort = PointsMap[location.ID].Sort
			l.PointsBackgroundImage = PointsMap[location.ID].BackgroundImage
			l.PointsValue = float32(PointsMap[location.ID].PointsValue)
		}
		rsp.List = append(rsp.List, l)
	}
	return rsp, nil
}

// DeviceTaskInfo 设备任务执行信息
func (*AdminService) DeviceTaskInfo(ctx context.Context, req *devicec.DeviceTaskInfoReq) (*devicec.DeviceTaskInfoRsp, error) {
	if req.Date == "" {
		req.Date = carbon.Yesterday().ToDateString()
	}
	rsp := &devicec.DeviceTaskInfoRsp{}
	if req.RobotId == "" && req.IdentificationCode == "" {
		rsp.Code = -1
		rsp.Msg = "设备id或设备识别码不能同时为空"
		return rsp, nil
	}

	var device models.Device
	db := mysql.NewDB()

	// 判断设备是否存在
	{
		rowsAffected := db.Model(&models.Device{}).
			Where("robot_id = ? OR identification_code = ?", req.RobotId, req.IdentificationCode).
			Take(&device).RowsAffected
		if rowsAffected == 0 {
			rsp.Code = -1
			rsp.Msg = "设备不存在"
			return rsp, nil
		}

	}
	date := carbon.Parse(req.Date).Layout("2006-1-2") // 活跃记录存的日期为2006-1-2格式，所以这里的格式需要转换
	// 获取活跃信息
	dm := dao.DeviceActiveDuration{}.Duration(ctx, []string{device.RobotID}, date)
	if dm[device.RobotID] == nil {
		rsp.Code = -1
		rsp.Msg = "无数据"
		return rsp, nil
	}
	// 返回完整任务数据
	rsp.TaskInfo = &devicec.DeviceTaskInfoRsp_TaskInfo{
		TotalTaskOdometer:       dm[device.RobotID].TaskOdometer,
		TotalTaskDuration:       dm[device.RobotID].TaskDuration,
		TotalTaskRunCount:       dm[device.RobotID].TaskRunCount,
		TotalChargingDuration:   dm[device.RobotID].ChargingDuration,
		TotalIdleDuration:       dm[device.RobotID].IdleDuration,
		TotalToTaskDuration:     dm[device.RobotID].ToTaskDuration,
		TotalToChargingDuration: dm[device.RobotID].ToChargingDuration,
	}
	return rsp, nil
}

// GetLocationInfo 获取位置Token信息
func (*AdminService) GetLocationInfo(ctx context.Context, req *devicec.LocationTokenInfoReq) (*devicec.LocationTokenInfoRsp, error) {
	rsp := &devicec.LocationTokenInfoRsp{}
	// 1. 鉴定token是否有效
	if req.Token != "" {
		// 从位置列表获取当前的Token是否存在
		var locations struct {
			ID       string `json:"id" gorm:"column:id"`
			Name     string `json:"name" gorm:"column:name"`
			Token    string `json:"token" gorm:"column:token"`
			RobotIds string `json:"robot_ids" gorm:"column:robot_ids"`
		}
		// 2. 获取可以查看机器
		rowsAffected := mysql.NewDB().Model(&models.Location{}).
			Where("token = ?", req.Token).
			Find(&locations).RowsAffected
		if rowsAffected <= 0 {
			rsp.Code = -1
			rsp.Msg = "token无效"
			return rsp, nil
		}
		if locations.RobotIds != "" {
			rsp.Code = 0
			rsp.Msg = "获取成功"
			rsp.RobotIds = locations.RobotIds
			rsp.Name = locations.Name
			rsp.Id = locations.ID
		}
		return rsp, nil

	} else {
		rsp.Code = -1
		rsp.Msg = "商场token不能为空"
		return rsp, nil
	}
}

type ChildType struct {
	ID       string      `json:"id"`
	PID      string      `json:"pid"`
	Name     string      `json:"name"`
	Level    int         `json:"level"`
	Children []ChildType `json:"children"`
}

func (*AdminService) AreaLocation(ctx context.Context, req *devicec.AreaLocationReq) (*devicec.AreaLocationRsp, error) {
	list, err := location.AreaLocationList(req.MerchantId, req.MerchantPath, req.MerchantIds, req.WithoutLocation, int(req.Level))

	var result []ChildType
	list1, _ := json.Marshal(list)
	json.Unmarshal(list1, &result)
	LocationRes := []*devicec.AreaLocationRsp_AreaLocation{}
	LocationRes = getLocationTree(LocationRes, result)
	res := &devicec.AreaLocationRsp{}
	res.Code = 0
	res.Data = LocationRes
	return res, err
}

func getLocationTree(data1 []*devicec.AreaLocationRsp_AreaLocation, data2 []ChildType) []*devicec.AreaLocationRsp_AreaLocation {
	for _, v := range data2 {
		item := &devicec.AreaLocationRsp_AreaLocation{}
		item.Id = v.ID
		item.Pid = v.PID
		item.Name = v.Name
		item.Level = int32(v.Level)
		if v.Children != nil {
			item.Children = getLocationTree(item.Children, v.Children)
		}
		data1 = append(data1, item)
	}
	return data1
}

func (*AdminService) AllDeviceList(ctx context.Context, req *devicec.AllDeviceListReq) (*devicec.AllDeviceListRsp, error) {
	fields := []string{"id", "robot_id", "online", "device_type", "identification_code", "merchant_id", "location_id"}
	deviceList, total, err := device.DeviceList(
		"1", // 广告系统查询所有设备
		"",
		req.RobotId,
		req.DeviceTypes,
		"",
		-1,
		int(req.Page),
		int(req.Limit),
		fields,
		req.ProvinceId, req.CityId, req.DistrictId, req.LocationId,
		0, 0, 0,
	)
	res := &devicec.AllDeviceListRsp{}
	if err != nil {
		res.Code = -1
		res.Msg = err.Error()
		return res, nil
	}
	for _, v := range deviceList {
		item := &devicec.AllDeviceListRsp_AllDeviceList{}
		item.Id = v.ID
		item.RobotId = v.RobotID
		item.Online = int32(v.Online)
		item.DeviceType = int32(v.DeviceType)
		item.IdentificationCode = v.IdentificationCode
		item.MerchantId = v.MerchantID
		item.LocationId = v.LocationID
		res.Data = append(res.Data, item)
	}
	res.Total = total
	return res, nil
}

// 接收广告平台的投放计划变更通知，并下发
func (*AdminService) NoticePublishScheduleChange(ctx context.Context, req *devicec.NoticePublishScheduleChangeReq) (*devicec.NoticePublishScheduleChangeRsp, error) {
	res := &devicec.NoticePublishScheduleChangeRsp{}

	logx.Info(context.Background(), "GetPublishSchedule in NoticePublishScheduleChange:"+req.Devices)
	terminal.GetPublishSchedule(req.Devices, true /*force*/, "NoticePublishScheduleChange")
	return res, nil
}

// RobotCurrentMap 机器当前使用的地图
func (*AdminService) RobotCurrentMap(ctx context.Context, req *devicec.RobotCurrentMapReq) (*devicec.RobotCurrentMapRsp, error) {
	rdb := redis.NewDB()
	rctx, rcancel := context.WithTimeout(context.Background(), time.Second*3)
	defer rcancel()
	mapName, _ := rdb.HGet(rctx, define.RedisRobotRTMap+req.RobotId, "name").Result()
	mapURL, _ := rdb.HGet(rctx, define.RedisRobotRTMap+req.RobotId, "url").Result()
	rsp := &devicec.RobotCurrentMapRsp{}
	rsp.Name = mapName
	rsp.Url = mapURL
	return rsp, nil
}

// RobotCurrentPosition 机器当前的位置
func (*AdminService) RobotCurrentPosition(ctx context.Context, req *devicec.RobotCurrentPositionReq) (*devicec.RobotCurrentPositionRsp, error) {
	rdb := redis.NewDB()
	rctx, rcancel := context.WithTimeout(context.Background(), time.Second*3)
	defer rcancel()
	px, _ := rdb.HGet(rctx, define.RedisRobotRtPosition+req.RobotId, "px").Result()
	py, _ := rdb.HGet(rctx, define.RedisRobotRtPosition+req.RobotId, "py").Result()
	rsp := &devicec.RobotCurrentPositionRsp{}
	rsp.X, _ = strconv.ParseInt(px, 10, 64)
	rsp.Y, _ = strconv.ParseInt(py, 10, 64)
	return rsp, nil
}

// RobotHeatmapMapList 热力图使用功的地图列表
func (*AdminService) RobotHeatmapMapList(ctx context.Context, req *devicec.RobotHeatmapMapListReq) (*devicec.RobotHeatmapMapListRsp, error) {
	list, _ := dao.Heatmap{}.MapList(ctx, dal.Q, req.RobotId, req.StartTime, req.EndTime)
	rsp := &devicec.RobotHeatmapMapListRsp{}
	for _, m := range list {
		var deviceMap models.DeviceMap
		found := mysql.NewDB().Model(&models.DeviceMap{}).Where("id = ?", m.MapID).Select("map_md5").Take(&deviceMap).RowsAffected
		if found > 0 {
			rsp.List = append(rsp.List, &devicec.RobotHeatmapMapListRsp_MapList{
				Name: m.MapName,
				Id:   m.MapID,
				Url:  path.Join("/file/device-admin/device-map", req.RobotId, deviceMap.MapMd5+".png"),
			})
		}
	}
	return rsp, nil
}

// RobotHeatmapData 热力图数据
func (*AdminService) RobotHeatmapData(ctx context.Context, req *devicec.RobotHeatmapDataReq) (*devicec.RobotHeatmapDataRsp, error) {
	list, _ := dao.Heatmap{}.PositionData(ctx, dal.Q, req.RobotId, req.MapId, req.StartTime, req.EndTime)
	rsp := &devicec.RobotHeatmapDataRsp{}
	for _, d := range list {
		var deviceMap models.DeviceMap
		found := mysql.NewDB().Model(&models.DeviceMap{}).Where("id = ?", req.MapId).Take(&deviceMap).RowsAffected
		if found > 0 {
			px := int64(d.PosX*deviceMap.R11 + d.PosY*deviceMap.R12 + deviceMap.Tx)
			py := int64(d.PosX*deviceMap.R21 + d.PosY*deviceMap.R22 + deviceMap.Ty)
			rsp.List = append(rsp.List, &devicec.RobotHeatmapDataRsp_PositionData{
				X:                  px,
				Y:                  py,
				CreatedOrderNumber: d.CreatedOrderNumber,
				PaidOrderNumber:    d.PaidOrderNumber,
				Exposure:           d.FaceCount,
				ExposureFront:      d.FaceCountFront,
				ExposureLeft:       d.FaceCountLeft,
				ExposureRight:      d.FaceCountRight,
			})
		}
	}
	return nil, status.Errorf(codes.Unimplemented, "method RobotHeatmapData not implemented")
}

// PlayTTS tts播放
func (*AdminService) PlayTTS(ctx context.Context, req *devicec.SendTTSReq) (*devicec.SendTTSRsp, error) {
	terminal.PlayTTS(req.RobotId, req.Text)
	return &devicec.SendTTSRsp{
		Code: 0,
	}, nil
}

// SendAlarmMsg 上报告警内容
func (*AdminService) SendAlarmMsg(ctx context.Context, req *devicec.SendAlarmMsgReq) (*devicec.SendAlarmMsgRsp, error) {
	// 增加告警监控
	alarm_monitor.AlarmMonitor{}.AddMonitor(
		req.RobotId,
		int(req.AlarmType),
		req.CreatedAt,
		int(req.DstStatus),
		false,
		req.Content, req.Battery,
	)
	rsp := &devicec.SendAlarmMsgRsp{}
	return rsp, nil
}

// LocationName 位置名称
func (*AdminService) LocationName(ctx context.Context, req *devicec.LocationNameReq) (*devicec.LocationNameRsp, error) {
	if len(req.LocationIds) <= 0 {
		return nil, errorx.New("位置id不能为空", -1)
	}
	nameMap := dao.Location{}.LocationNameMap(ctx, req.LocationIds)
	rsp := &devicec.LocationNameRsp{}
	rsp.LocationNameMap = nameMap
	return rsp, nil
}

// FloorName 楼层名称
func (*AdminService) FloorName(ctx context.Context, req *devicec.FloorNameReq) (*devicec.FloorNameRsp, error) {
	if len(req.FloorIds) <= 0 {
		return nil, errorx.New("楼层id不能为空", -1)
	}
	nameMap := dao.Floor{}.FloorNameMap(ctx, req.FloorIds)
	rsp := &devicec.FloorNameRsp{}
	rsp.FloorNameMap = nameMap
	return rsp, nil
}

// DevicePositionName 设备位置名称
func (*AdminService) DevicePositionName(ctx context.Context, req *devicec.DevicePositionNameReq) (*devicec.DevicePositionNameRsp, error) {
	if len(req.PositionIds) <= 0 {
		return nil, errorx.New("位置id不能为空", -1)
	}
	nameMap := dao.DevicePosition{}.PositionNameMap(ctx, req.PositionIds)
	rsp := &devicec.DevicePositionNameRsp{}
	rsp.DevicePositionNameMap = nameMap
	return rsp, nil
}
